library("tidyverse")
library("ggplot2")
library(plyr)
run_1 = read_csv('/Users/b1017579/Documents/PhD/Projects/10. ELECSIM/run/validation-optimisation/data/run_2.csv')
Missing column names filled in: 'X1' [1]Parsed with column specification:
cols(
X1 = col_double(),
id = col_double(),
run_number = col_double(),
time_taken = col_double(),
timestamp_start = col_double(),
timestamp_end = col_double(),
reward = col_double(),
individual_m = col_double(),
individual_c = col_double(),
coal = col_double(),
nuclear = col_double(),
ccgt = col_double(),
wind = col_double(),
solar = col_double()
)
tail(run_1)
ggplot(filter(run_1), aes(y=individual_m, x=individual_c)) + geom_hex(bins=10)

p = ggplot(run_1, aes(y=individual_m, x=individual_c, color=reward), size=10) + facet_wrap(~run_number) + geom_point()
p$labels$fill <- "Absolute \nPercentage \nError"
print(p)

p = ggplot(run_1, aes(y=individual_m, x=individual_c, color=reward), size=10) + facet_wrap(~run_number) + geom_jitter()
p$labels$fill <- "Absolute \nPercentage \nError"
print(p)

p = ggplot(run_1, aes(y=individual_m, x=individual_c, color=time_taken), size=10) + facet_wrap(~run_number) + geom_jitter()
p$labels$fill <- "Time \nTaken"
print(p)

p=ggplot(run_1, aes(y=individual_m, x=individual_c)) + stat_summary_hex(aes(z = reward), bins=10) + facet_wrap(~ run_number) + scale_x_continuous(breaks = round(seq(min(run_1$individual_c), max(run_1$individual_c), by = 25),1))
p$labels$fill <- "Absolute \nPercentage \nError"
print(p)
ggsave("~/Desktop/genetic_algorithm_progression.png")
Saving 6.89 x 4.26 in image


# accurate_area = filter(run_1, individual_c<-4.8, individual_c>-50, individual_m <0.003, individual_m > 0.0023)
# accurate_area = filter(run_1, reward < 0.2)
# accurate_area = filter(run_1, run_number==11)
accurate_area = filter(run_1, run_number == 16)
accurate_area
ggplot(data=accurate_area, aes(x=individual_c, y=individual_m, color=reward))+geom_jitter()+geom_point(color="red", alpha=0.5)

accurate_area_long = gather(accurate_area, "key", "value", "coal", "ccgt", "wind", "nuclear", "solar")
accurate_area_long_perc = accurate_area_long %>% group_by(id) %>% mutate(value_perc = value/sum(value))
params_more_than_10 = run_1 %>% group_by(individual_c, individual_m) %>% mutate(number = n()) %>% ungroup() %>% filter(number> 10) %>% mutate_if(is.numeric, round, 6)
params_more_than_10 %>% ggplot(aes(as.factor(individual_c), reward)) +geom_violin()+facet_wrap(~individual_m)+ theme(axis.text.x = element_text(angle = 90, hjust = 1)) +geom_jitter(position=position_jitter(0.2), alpha=0.05)

accurate_area %>% mutate_if(is.numeric, round, 6) %>% ggplot(aes(as.factor(individual_c), reward)) +geom_violin()+facet_wrap(~individual_m)+ theme(axis.text.x = element_text(angle = 90, hjust = 1)) +geom_jitter(position=position_jitter(0.2))

actual_mix = read_csv('/Users/b1017579/Documents/PhD/Projects/10. ELECSIM/elecsim/data/processed/electricity_mix/energy_mix_historical.csv')
Missing column names filled in: 'X1' [1]Parsed with column specification:
cols(
X1 = col_double(),
year = col_double(),
variable = col_character(),
value = col_double()
)
actual_mix_2018 = filter(actual_mix, year==2018)
actual_mix_2018$type = "actual"
actual_mix_2018
actual_mix_2018_reduced = filter(actual_mix_2018, variable %in% c("ccgt", 'wind', 'nuclear', 'solar', 'coal'))
actual_mix_2018_reduced = actual_mix_2018_reduced %>% mutate(value_perc = value/sum(value))
actual_mix_2018_reduced = dplyr::rename(actual_mix_2018_reduced, key=variable)
head(actual_mix_2018_reduced)
accurate_area_long_perc$type = 'predicted'
accurate_area_long_perc
comparison = rbind(select(ungroup(accurate_area_long_perc), "key", "type", "value", 'value_perc'), select(actual_mix_2018_reduced, -X1, -year))
ggplot(comparison, aes(x=key, y=value_perc, fill=type)) +
stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + ylab("Energy Mix (%)") + ggtitle("Combination of all runs in last genetic algorithm run")
ggsave('~/Desktop/average_error_of_best_params.png')
Saving 6.89 x 4.26 in image

best_result = run_1 %>% group_by("id") %>% filter(reward==min(reward)) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% mutate(type="predicted")
comparison_best = rbind(select(ungroup(best_result), "key", "type", "value", 'value_perc'), select(actual_mix_2018_reduced, -X1, -year))
ggplot(comparison_best, aes(x=key, y=value_perc, fill=type)) + geom_col(position = "dodge2") + ylab("Energy Mix (%)") + ggtitle("Best single run")

pdc_example = read_csv('/Users/b1017579/Documents/PhD/Projects/10. ELECSIM/notebooks/validation-optimisation/data/price_demand_curve_example.csv')
Missing column names filled in: 'X1' [1]Parsed with column specification:
cols(
X1 = col_double(),
accepted_price = col_double(),
segment_demand = col_double(),
segment_hour = col_double()
)
demand_range = data.frame(x=seq(from=min(pdc_example$segment_demand),max(pdc_example$segment_demand),length.out=500))
get_line = function(c, m){
y = m * demand_range + c
return(y)
}
get_x = function(){
return(demand_range)
}
# lines = accurate_area %>% group_by(id) %>% apply(get_line(.))
lines = ddply(accurate_area, .(id), transform, y=get_line(individual_c, individual_m), x=get_x())
ggplot() + geom_line(data=lines, aes(x=x.1, y=x, group=id, color=reward)) + stat_smooth(data=pdc_example, aes(x=segment_demand, y=accepted_price), method="lm", col="red") + geom_point(data=pdc_example, aes(x=segment_demand, y=accepted_price)) + xlab("Demand (MW)") + ylab("Accepted Price") + ggtitle("Last genetic algorithm run price curves between 2023-2028 \ncompared to price in 2018")

best_75_percentile = accurate_area %>% group_by(individual_c, individual_m) %>% summarise(number=n(), quantiles = list(enframe(quantile(reward, probs=c(0.25,0.5,0.75))))) %>% unnest %>% ungroup() %>% filter(number>10) %>% group_by(name) %>%filter(rank(value, ties.method="first")==1)
p = ddply(best_75_percentile, .(name), transform, y=get_line(individual_c, individual_m), x=get_x()) %>% ggplot() + geom_line(aes(x=x.1, y=x, color=name))+ stat_smooth(data=pdc_example, aes(x=segment_demand, y=accepted_price), method="lm", col="yellow") + geom_point(data=pdc_example, aes(x=segment_demand, y=accepted_price)) + xlab("Demand (MW)") + ylab("Accepted Price") + ggtitle("Price curve of best runs at different percentiles of last genetic \nalgorithm population")
p$labels$fill <- "Percentiles"
print(p)

best_percentiles_comparison = inner_join(accurate_area, best_75_percentile, by=c('individual_c', 'individual_m')) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% mutate(type=name)
comparison_percentiles = rbind(select(ungroup(best_percentiles_comparison), "key", "type", "value", 'value_perc'), select(actual_mix_2018_reduced, -X1, -year))
comparison_percentiles %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + ggtitle("Comparison of energy mix of final genetic algorithm population \nat different quantile levels")

rbind(filter(select(ungroup(best_percentiles_comparison), "key", "type", "value", 'value_perc'),type=="50%"), select(actual_mix_2018_reduced, -X1, -year)) %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + ggtitle("Best parameter combinations from final genetic algorithm run")

BEST PARAMETER COMBINATION FROM ALL RUNS
best_75_percentile_all_runs = run_1 %>% group_by(individual_c, individual_m) %>% summarise(number=n(), quantiles = list(enframe(quantile(reward, probs=c(0.25,0.5,0.75, 0.9, 1.0))))) %>% unnest %>% ungroup() %>% filter(number>10) %>% group_by(name) %>%filter(rank(value, ties.method="first")==1)
p = ddply(best_75_percentile_all_runs, .(name), transform, y=get_line(individual_c, individual_m), x=get_x()) %>% ggplot() + geom_line(aes(x=x.1, y=x, color=name))+ stat_smooth(data=pdc_example, aes(x=segment_demand, y=accepted_price), method="lm", col="yellow") + geom_point(data=pdc_example, aes(x=segment_demand, y=accepted_price)) + xlab("Demand (MW)") + ylab("Accepted Price") + ggtitle("Percentiles from all runs")
p$labels$fill <- "Percentiles"
print(p)

best_percentiles_comparison = inner_join(run_1, best_75_percentile_all_runs, by=c('individual_c', 'individual_m')) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% mutate(type=name)
comparison_percentiles = rbind(select(ungroup(best_percentiles_comparison), "key", "type", "value", 'value_perc'), select(actual_mix_2018_reduced, -X1, -year))
comparison_percentiles %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2")

rbind(filter(select(ungroup(best_percentiles_comparison), "key", "type", "value", 'value_perc'),type=="75%" | type=="90%"), select(actual_mix_2018_reduced, -X1, -year)) %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2") + ggtitle("Best parameter combinations from all genetic algorithm runs")

Calculation of best quantile to select combination of parameters for lowest reward
best_params = params_more_than_10 %>% group_by(individual_c, individual_m) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% group_by(id, key) %>% inner_join(actual_mix_2018_reduced, by="key") %>% mutate(diff_perc = abs(value_perc.x-value_perc.y)) %>% group_by(id) %>% summarise(mean_diff_perc = mean(diff_perc), individual_c = mean(individual_c), individual_m = mean(individual_m)) %>% filter(rank(mean_diff_perc, ties.method="first")==1)
best_params_comparison = filter(run_1,near(x=individual_c, y=best_params$individual_c[1], tol=0.00001),near(x=individual_m, y=best_params$individual_m[1], tol=0.00001)) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% mutate(type="predicted")
comparison_best_params = rbind(select(ungroup(best_params_comparison), "key", "type", "value", 'value_perc'), select(actual_mix_2018_reduced, -X1, -year))
comparison_best_params %>% ggplot(aes(x=key, y=value_perc, fill=type)) + stat_summary(geom = "bar", fun.y = mean, position = "dodge2")+ stat_summary(geom = "errorbar", fun.data = mean_se, position = "dodge2")

## Getting MAPE for best param
predicted_means_best_param = filter(run_1,near(x=individual_c, y=best_params$individual_c[1], tol=0.00001),near(x=individual_m, y=best_params$individual_m[1], tol=0.00001)) %>% gather("key", "value", "coal", "ccgt", "wind", "nuclear", "solar") %>% group_by(id) %>% mutate(value_perc = value/sum(value)) %>% mutate(type="predicted") %>% group_by(individual_c, individual_m, key) %>% summarise(predicted_perc = mean(value_perc))
print(paste("MAPE = ", MAPE(predicted_means_best_param$predicted_perc, actual_mix_2018_reduced$value_perc)))
[1] "MAPE = 1.86812340451084"
p = best_params %>% transform(y=get_line(individual_c, individual_m), x=get_x()) %>% ggplot() + geom_line(aes(x=x.1, y=x))+ stat_smooth(data=pdc_example, aes(x=segment_demand, y=accepted_price), method="lm", col="yellow") + geom_point(data=pdc_example, aes(x=segment_demand, y=accepted_price)) + xlab("Demand (MW)") + ylab("Accepted Price") + ggtitle("Best paramater combination price curve")
p$labels$fill <- "Percentiles"
print(p)

accurate_area %>% ggplot() + geom_histogram(aes(individual_m)) + xlim(0.0015, 0.0025)

accurate_area %>% ggplot(aes(individual_c)) + geom_histogram()

p=ggplot(run_1, aes(y=individual_m, x=individual_c)) + stat_summary_hex(aes(z = time_taken), bins=10) + facet_wrap(~ run_number) + scale_x_continuous(breaks = round(seq(min(run_1$individual_c), max(run_1$individual_c), by = 25),1))
p$labels$fill <- "Time \n Taken"
print(p)
ggsave('~/Desktop/time-taken-parameters.png')
Saving 6.89 x 4.26 in image

run_1 %>% arrange(desc(run_number)) %>% ggplot(alpha=0.1, aes(y=individual_m, x=individual_c)) + geom_point(aes(color=run_number, size=time_taken))

run_1 %>% filter(individual_c<-3, individual_m <0.0025, individual_m > 0.0015) %>% ggplot(aes(x=as.factor(run_number), y=time_taken)) + geom_violin()

ggplot(data=run_1, aes(x=as.factor(run_number), y=reward))+geom_boxplot()+geom_jitter(position=position_jitter(0.2), alpha=0.5, color='blue')

run_1 %>% filter(individual_c<-3, individual_m <0.0025, individual_m > 0.0015) %>% ggplot(aes(x=as.factor(run_number), y=reward)) + geom_violin()

ggplot(run_1, aes(x=reward, y=time_taken))+stat_smooth(method = "lm")+geom_point()+xlab("Absolute Percentage Error")

dif_sum = run_1_long %>% inner_join(actual_mix_2018_reduced, by='key') %>% group_by(id,key) %>% mutate(total_difference = value.x-value.y) %>% ddply(.(id, key), summarise, difference_sum = sum(total_difference), time_taken=time_taken, reward=reward, run_number=run_number) %>% group_by(id) %>% summarise(tot_diff = sum(difference_sum), time_taken=mean(time_taken), reward=mean(reward), run_number=mean(run_number))
plot_ly(data=dif_sum, x=~tot_diff, y=~time_taken, z=~reward, type="scatter3d", mode="markers", color=~run_number)
ggplot()+geom_point(data=run_1, aes(x=time_taken, y=reward, color=run_number))

run_1
run_1_long = gather(run_1, "key", "value", "coal", "ccgt", "wind", "nuclear", "solar")
# run_1_long %>% inner_join(actual_mix_2018_reduced, by='key') %>% group_by(id,key) %>% mutate(total_difference = value.x-value.y) %>% group_by(id, key) %>% summarise(difference_sum = sum(total_difference))
ggplot(data=dif_sum, aes(x=tot_diff, y=time_taken, color=reward)) + geom_point()+geom_smooth()+xlim(-11000,10000)

# actual_mix_2018_reduced
ggplot(data=dif_sum, aes(x=tot_diff, color=time_taken, y=reward)) + geom_point()+geom_smooth()+xlim(-11000,10000)

ggplot(run_1, aes(x=run_number, y=time_taken, color=reward)) + geom_point() +
geom_smooth(method='lm')

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCmBgYHtyfQpsaWJyYXJ5KCJ0aWR5dmVyc2UiKQpsaWJyYXJ5KCJnZ3Bsb3QyIikKbGlicmFyeShwbHlyKQpgYGAKYGBge3J9CnJ1bl8xID0gcmVhZF9jc3YoJy9Vc2Vycy9iMTAxNzU3OS9Eb2N1bWVudHMvUGhEL1Byb2plY3RzLzEwLiBFTEVDU0lNL3J1bi92YWxpZGF0aW9uLW9wdGltaXNhdGlvbi9kYXRhL3J1bl8yLmNzdicpCnRhaWwocnVuXzEpCmBgYApgYGB7cn0KZ2dwbG90KGZpbHRlcihydW5fMSksIGFlcyh5PWluZGl2aWR1YWxfbSwgeD1pbmRpdmlkdWFsX2MpKSArIGdlb21faGV4KGJpbnM9MTApCmBgYAoKYGBge3J9CnAgPSBnZ3Bsb3QocnVuXzEsIGFlcyh5PWluZGl2aWR1YWxfbSwgeD1pbmRpdmlkdWFsX2MsIGNvbG9yPXJld2FyZCksIHNpemU9MTApICsgZmFjZXRfd3JhcCh+cnVuX251bWJlcikgKyBnZW9tX3BvaW50KCkKcCRsYWJlbHMkZmlsbCA8LSAiQWJzb2x1dGUgXG5QZXJjZW50YWdlIFxuRXJyb3IiCnByaW50KHApCmBgYApgYGB7cn0KcCA9IGdncGxvdChydW5fMSwgYWVzKHk9aW5kaXZpZHVhbF9tLCB4PWluZGl2aWR1YWxfYywgY29sb3I9cmV3YXJkKSwgc2l6ZT0xMCkgKyBmYWNldF93cmFwKH5ydW5fbnVtYmVyKSArIGdlb21faml0dGVyKCkKcCRsYWJlbHMkZmlsbCA8LSAiQWJzb2x1dGUgXG5QZXJjZW50YWdlIFxuRXJyb3IiCnByaW50KHApCmBgYAoKCmBgYHtyfQpwID0gZ2dwbG90KHJ1bl8xLCBhZXMoeT1pbmRpdmlkdWFsX20sIHg9aW5kaXZpZHVhbF9jLCBjb2xvcj10aW1lX3Rha2VuKSwgc2l6ZT0xMCkgKyBmYWNldF93cmFwKH5ydW5fbnVtYmVyKSArIGdlb21faml0dGVyKCkKcCRsYWJlbHMkZmlsbCA8LSAiVGltZSBcblRha2VuIgpwcmludChwKQoKYGBgCgpgYGB7cn0KcD1nZ3Bsb3QocnVuXzEsIGFlcyh5PWluZGl2aWR1YWxfbSwgeD1pbmRpdmlkdWFsX2MpKSArIHN0YXRfc3VtbWFyeV9oZXgoYWVzKHogPSByZXdhcmQpLCBiaW5zPTEwKSArIGZhY2V0X3dyYXAofiBydW5fbnVtYmVyKSArIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSByb3VuZChzZXEobWluKHJ1bl8xJGluZGl2aWR1YWxfYyksIG1heChydW5fMSRpbmRpdmlkdWFsX2MpLCBieSA9IDI1KSwxKSkKcCRsYWJlbHMkZmlsbCA8LSAiQWJzb2x1dGUgXG5QZXJjZW50YWdlIFxuRXJyb3IiCnByaW50KHApCgpnZ3NhdmUoIn4vRGVza3RvcC9nZW5ldGljX2FsZ29yaXRobV9wcm9ncmVzc2lvbi5wbmciKQpgYGAKCmBgYHtyfQpydW5fMSAlPiUgZ3JvdXBfYnkocnVuX251bWJlcikgJT4lIHN1bW1hcmlzZShhdmdfcmV3YXJkID0gbWVhbihyZXdhcmQpKSAlPiUgZ2dwbG90KCkrIGdlb21fc21vb3RoKGRhdGE9cnVuXzEsIGFlcyh4PXJ1bl9udW1iZXIsIHJld2FyZCkpK2dlb21fbGluZShhZXMoeD1ydW5fbnVtYmVyLCB5PWF2Z19yZXdhcmQpKSAKYGBgCgoKCgoKYGBge3J9CiMgYWNjdXJhdGVfYXJlYSA9IGZpbHRlcihydW5fMSwgaW5kaXZpZHVhbF9jPC00LjgsIGluZGl2aWR1YWxfYz4tNTAsIGluZGl2aWR1YWxfbSA8MC4wMDMsIGluZGl2aWR1YWxfbSA+IDAuMDAyMykKIyBhY2N1cmF0ZV9hcmVhID0gZmlsdGVyKHJ1bl8xLCByZXdhcmQgPCAwLjIpCiMgYWNjdXJhdGVfYXJlYSA9IGZpbHRlcihydW5fMSwgcnVuX251bWJlcj09MTEpCmFjY3VyYXRlX2FyZWEgPSBmaWx0ZXIocnVuXzEsIHJ1bl9udW1iZXIgPT0gMTYpCgphY2N1cmF0ZV9hcmVhCgpgYGAKCmBgYHtyfQogZ2dwbG90KGRhdGE9YWNjdXJhdGVfYXJlYSwgYWVzKHg9aW5kaXZpZHVhbF9jLCB5PWluZGl2aWR1YWxfbSwgY29sb3I9cmV3YXJkKSkrZ2VvbV9qaXR0ZXIoKStnZW9tX3BvaW50KGNvbG9yPSJyZWQiLCBhbHBoYT0wLjUpCgpgYGAKCgpgYGB7cn0KYWNjdXJhdGVfYXJlYV9sb25nID0gZ2F0aGVyKGFjY3VyYXRlX2FyZWEsICJrZXkiLCAidmFsdWUiLCAiY29hbCIsICJjY2d0IiwgIndpbmQiLCAibnVjbGVhciIsICJzb2xhciIpCmFjY3VyYXRlX2FyZWFfbG9uZ19wZXJjID0gYWNjdXJhdGVfYXJlYV9sb25nICU+JSBncm91cF9ieShpZCkgJT4lIG11dGF0ZSh2YWx1ZV9wZXJjID0gdmFsdWUvc3VtKHZhbHVlKSkKCmBgYAoKCmBgYHtyfQpwYXJhbXNfbW9yZV90aGFuXzEwID0gcnVuXzEgJT4lIGdyb3VwX2J5KGluZGl2aWR1YWxfYywgaW5kaXZpZHVhbF9tKSAlPiUgbXV0YXRlKG51bWJlciA9IG4oKSkgJT4lIHVuZ3JvdXAoKSAlPiUgZmlsdGVyKG51bWJlcj4gMTApICU+JSBtdXRhdGVfaWYoaXMubnVtZXJpYywgcm91bmQsIDYpIAoKcGFyYW1zX21vcmVfdGhhbl8xMCAlPiUgZ2dwbG90KGFlcyhhcy5mYWN0b3IoaW5kaXZpZHVhbF9jKSwgcmV3YXJkKSkgK2dlb21fdmlvbGluKCkrZmFjZXRfd3JhcCh+aW5kaXZpZHVhbF9tKSsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkgK2dlb21faml0dGVyKHBvc2l0aW9uPXBvc2l0aW9uX2ppdHRlcigwLjIpLCBhbHBoYT0wLjA1KQoKYGBgCgpgYGB7cn0KYWNjdXJhdGVfYXJlYSAlPiUgbXV0YXRlX2lmKGlzLm51bWVyaWMsIHJvdW5kLCA2KSAlPiUgZ2dwbG90KGFlcyhhcy5mYWN0b3IoaW5kaXZpZHVhbF9jKSwgcmV3YXJkKSkgK2dlb21fdmlvbGluKCkrZmFjZXRfd3JhcCh+aW5kaXZpZHVhbF9tKSsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkgK2dlb21faml0dGVyKHBvc2l0aW9uPXBvc2l0aW9uX2ppdHRlcigwLjIpKQpgYGAKCgpgYGB7cn0KYWN0dWFsX21peCA9IHJlYWRfY3N2KCcvVXNlcnMvYjEwMTc1NzkvRG9jdW1lbnRzL1BoRC9Qcm9qZWN0cy8xMC4gRUxFQ1NJTS9lbGVjc2ltL2RhdGEvcHJvY2Vzc2VkL2VsZWN0cmljaXR5X21peC9lbmVyZ3lfbWl4X2hpc3RvcmljYWwuY3N2JykKYWN0dWFsX21peF8yMDE4ID0gZmlsdGVyKGFjdHVhbF9taXgsIHllYXI9PTIwMTgpCgoKYWN0dWFsX21peF8yMDE4JHR5cGUgPSAiYWN0dWFsIgoKYWN0dWFsX21peF8yMDE4CmBgYAoKYGBge3J9CmFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkID0gZmlsdGVyKGFjdHVhbF9taXhfMjAxOCwgdmFyaWFibGUgJWluJSBjKCJjY2d0IiwgJ3dpbmQnLCAnbnVjbGVhcicsICdzb2xhcicsICdjb2FsJykpCmFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkID0gYWN0dWFsX21peF8yMDE4X3JlZHVjZWQgJT4lIG11dGF0ZSh2YWx1ZV9wZXJjID0gdmFsdWUvc3VtKHZhbHVlKSkKYWN0dWFsX21peF8yMDE4X3JlZHVjZWQgPSBkcGx5cjo6cmVuYW1lKGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCBrZXk9dmFyaWFibGUpCmhlYWQoYWN0dWFsX21peF8yMDE4X3JlZHVjZWQpCmBgYApgYGB7cn0KYWNjdXJhdGVfYXJlYV9sb25nX3BlcmMkdHlwZSA9ICdwcmVkaWN0ZWQnCmFjY3VyYXRlX2FyZWFfbG9uZ19wZXJjCgpjb21wYXJpc29uID0gcmJpbmQoc2VsZWN0KHVuZ3JvdXAoYWNjdXJhdGVfYXJlYV9sb25nX3BlcmMpLCAia2V5IiwgInR5cGUiLCAidmFsdWUiLCAndmFsdWVfcGVyYycpLCBzZWxlY3QoYWN0dWFsX21peF8yMDE4X3JlZHVjZWQsIC1YMSwgLXllYXIpKQpgYGAKCgoKCgpgYGB7cn0KZ2dwbG90KGNvbXBhcmlzb24sIGFlcyh4PWtleSwgeT12YWx1ZV9wZXJjLCBmaWxsPXR5cGUpKSArCiAgc3RhdF9zdW1tYXJ5KGdlb20gPSAiYmFyIiwgZnVuLnkgPSBtZWFuLCBwb3NpdGlvbiA9ICJkb2RnZTIiKSsgc3RhdF9zdW1tYXJ5KGdlb20gPSAiZXJyb3JiYXIiLCBmdW4uZGF0YSA9IG1lYW5fc2UsIHBvc2l0aW9uID0gImRvZGdlMiIpICsgeWxhYigiRW5lcmd5IE1peCAoJSkiKSArIGdndGl0bGUoIkNvbWJpbmF0aW9uIG9mIGFsbCBydW5zIGluIGxhc3QgZ2VuZXRpYyBhbGdvcml0aG0gcnVuIikKCmdnc2F2ZSgnfi9EZXNrdG9wL2F2ZXJhZ2VfZXJyb3Jfb2ZfYmVzdF9wYXJhbXMucG5nJykKYGBgCgpgYGB7cn0KYmVzdF9yZXN1bHQgPSBydW5fMSAlPiUgZ3JvdXBfYnkoImlkIikgJT4lIGZpbHRlcihyZXdhcmQ9PW1pbihyZXdhcmQpKSAlPiUgZ2F0aGVyKCJrZXkiLCAidmFsdWUiLCAiY29hbCIsICJjY2d0IiwgIndpbmQiLCAibnVjbGVhciIsICJzb2xhciIpICU+JSBncm91cF9ieShpZCkgJT4lIG11dGF0ZSh2YWx1ZV9wZXJjID0gdmFsdWUvc3VtKHZhbHVlKSkgJT4lIG11dGF0ZSh0eXBlPSJwcmVkaWN0ZWQiKQoKY29tcGFyaXNvbl9iZXN0ID0gcmJpbmQoc2VsZWN0KHVuZ3JvdXAoYmVzdF9yZXN1bHQpLCAia2V5IiwgInR5cGUiLCAidmFsdWUiLCAndmFsdWVfcGVyYycpLCBzZWxlY3QoYWN0dWFsX21peF8yMDE4X3JlZHVjZWQsIC1YMSwgLXllYXIpKQoKZ2dwbG90KGNvbXBhcmlzb25fYmVzdCwgYWVzKHg9a2V5LCB5PXZhbHVlX3BlcmMsIGZpbGw9dHlwZSkpICsgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UyIikgKyB5bGFiKCJFbmVyZ3kgTWl4ICglKSIpICsgZ2d0aXRsZSgiQmVzdCBzaW5nbGUgcnVuIikKYGBgCgoKYGBge3J9CnBkY19leGFtcGxlID0gcmVhZF9jc3YoJy9Vc2Vycy9iMTAxNzU3OS9Eb2N1bWVudHMvUGhEL1Byb2plY3RzLzEwLiBFTEVDU0lNL25vdGVib29rcy92YWxpZGF0aW9uLW9wdGltaXNhdGlvbi9kYXRhL3ByaWNlX2RlbWFuZF9jdXJ2ZV9leGFtcGxlLmNzdicpCgoKZGVtYW5kX3JhbmdlID0gZGF0YS5mcmFtZSh4PXNlcShmcm9tPW1pbihwZGNfZXhhbXBsZSRzZWdtZW50X2RlbWFuZCksbWF4KHBkY19leGFtcGxlJHNlZ21lbnRfZGVtYW5kKSxsZW5ndGgub3V0PTUwMCkpCgpnZXRfbGluZSA9IGZ1bmN0aW9uKGMsIG0pewogICAgeSA9IG0gKiBkZW1hbmRfcmFuZ2UgKyBjCiAgICByZXR1cm4oeSkKfQoKZ2V0X3ggPSBmdW5jdGlvbigpewogICAgcmV0dXJuKGRlbWFuZF9yYW5nZSkKfQoKIyBsaW5lcyA9IGFjY3VyYXRlX2FyZWEgJT4lIGdyb3VwX2J5KGlkKSAlPiUgYXBwbHkoZ2V0X2xpbmUoLikpCgpsaW5lcyA9IGRkcGx5KGFjY3VyYXRlX2FyZWEsIC4oaWQpLCB0cmFuc2Zvcm0sIHk9Z2V0X2xpbmUoaW5kaXZpZHVhbF9jLCBpbmRpdmlkdWFsX20pLCB4PWdldF94KCkpCgoKZ2dwbG90KCkgKyBnZW9tX2xpbmUoZGF0YT1saW5lcywgYWVzKHg9eC4xLCB5PXgsIGdyb3VwPWlkLCBjb2xvcj1yZXdhcmQpKSArIHN0YXRfc21vb3RoKGRhdGE9cGRjX2V4YW1wbGUsIGFlcyh4PXNlZ21lbnRfZGVtYW5kLCB5PWFjY2VwdGVkX3ByaWNlKSwgbWV0aG9kPSJsbSIsIGNvbD0icmVkIikgKyBnZW9tX3BvaW50KGRhdGE9cGRjX2V4YW1wbGUsIGFlcyh4PXNlZ21lbnRfZGVtYW5kLCB5PWFjY2VwdGVkX3ByaWNlKSkgKyB4bGFiKCJEZW1hbmQgKE1XKSIpICsgeWxhYigiQWNjZXB0ZWQgUHJpY2UiKSArIGdndGl0bGUoIkxhc3QgZ2VuZXRpYyBhbGdvcml0aG0gcnVuIHByaWNlIGN1cnZlcyBiZXR3ZWVuIDIwMjMtMjAyOCBcbmNvbXBhcmVkIHRvIHByaWNlIGluIDIwMTgiKQoKYGBgCgpgYGB7cn0KYmVzdF83NV9wZXJjZW50aWxlID0gYWNjdXJhdGVfYXJlYSAlPiUgZ3JvdXBfYnkoaW5kaXZpZHVhbF9jLCBpbmRpdmlkdWFsX20pICU+JSBzdW1tYXJpc2UobnVtYmVyPW4oKSwgcXVhbnRpbGVzID0gbGlzdChlbmZyYW1lKHF1YW50aWxlKHJld2FyZCwgcHJvYnM9YygwLjI1LDAuNSwwLjc1KSkpKSkgJT4lIHVubmVzdCAlPiUgdW5ncm91cCgpICU+JSBmaWx0ZXIobnVtYmVyPjEwKSAlPiUgZ3JvdXBfYnkobmFtZSkgJT4lZmlsdGVyKHJhbmsodmFsdWUsIHRpZXMubWV0aG9kPSJmaXJzdCIpPT0xKQoKcCA9IGRkcGx5KGJlc3RfNzVfcGVyY2VudGlsZSwgLihuYW1lKSwgdHJhbnNmb3JtLCB5PWdldF9saW5lKGluZGl2aWR1YWxfYywgaW5kaXZpZHVhbF9tKSwgeD1nZXRfeCgpKSAgJT4lIGdncGxvdCgpICsgZ2VvbV9saW5lKGFlcyh4PXguMSwgeT14LCBjb2xvcj1uYW1lKSkrIHN0YXRfc21vb3RoKGRhdGE9cGRjX2V4YW1wbGUsIGFlcyh4PXNlZ21lbnRfZGVtYW5kLCB5PWFjY2VwdGVkX3ByaWNlKSwgbWV0aG9kPSJsbSIsIGNvbD0ieWVsbG93IikgKyBnZW9tX3BvaW50KGRhdGE9cGRjX2V4YW1wbGUsIGFlcyh4PXNlZ21lbnRfZGVtYW5kLCB5PWFjY2VwdGVkX3ByaWNlKSkgKyB4bGFiKCJEZW1hbmQgKE1XKSIpICsgeWxhYigiQWNjZXB0ZWQgUHJpY2UiKSArIGdndGl0bGUoIlByaWNlIGN1cnZlIG9mIGJlc3QgcnVucyBhdCBkaWZmZXJlbnQgcGVyY2VudGlsZXMgb2YgbGFzdCBnZW5ldGljIFxuYWxnb3JpdGhtIHBvcHVsYXRpb24iKQpwJGxhYmVscyRmaWxsIDwtICJQZXJjZW50aWxlcyIKcHJpbnQocCkKYGBgCmBgYHtyfQpiZXN0X3BlcmNlbnRpbGVzX2NvbXBhcmlzb24gPSBpbm5lcl9qb2luKGFjY3VyYXRlX2FyZWEsIGJlc3RfNzVfcGVyY2VudGlsZSwgYnk9YygnaW5kaXZpZHVhbF9jJywgJ2luZGl2aWR1YWxfbScpKSAlPiUgZ2F0aGVyKCJrZXkiLCAidmFsdWUiLCAiY29hbCIsICJjY2d0IiwgIndpbmQiLCAibnVjbGVhciIsICJzb2xhciIpICU+JSBncm91cF9ieShpZCkgJT4lIG11dGF0ZSh2YWx1ZV9wZXJjID0gdmFsdWUvc3VtKHZhbHVlKSkgJT4lIG11dGF0ZSh0eXBlPW5hbWUpCgpjb21wYXJpc29uX3BlcmNlbnRpbGVzID0gcmJpbmQoc2VsZWN0KHVuZ3JvdXAoYmVzdF9wZXJjZW50aWxlc19jb21wYXJpc29uKSwgImtleSIsICJ0eXBlIiwgInZhbHVlIiwgJ3ZhbHVlX3BlcmMnKSwgc2VsZWN0KGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCAtWDEsIC15ZWFyKSkKCmNvbXBhcmlzb25fcGVyY2VudGlsZXMgJT4lIGdncGxvdChhZXMoeD1rZXksIHk9dmFsdWVfcGVyYywgZmlsbD10eXBlKSkgKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJiYXIiLCBmdW4ueSA9IG1lYW4sIHBvc2l0aW9uID0gImRvZGdlMiIpKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJlcnJvcmJhciIsIGZ1bi5kYXRhID0gbWVhbl9zZSwgcG9zaXRpb24gPSAiZG9kZ2UyIikgKyBnZ3RpdGxlKCJDb21wYXJpc29uIG9mIGVuZXJneSBtaXggb2YgZmluYWwgZ2VuZXRpYyBhbGdvcml0aG0gcG9wdWxhdGlvbiBcbmF0IGRpZmZlcmVudCBxdWFudGlsZSBsZXZlbHMiKQoKCmBgYApgYGB7cn0KcmJpbmQoZmlsdGVyKHNlbGVjdCh1bmdyb3VwKGJlc3RfcGVyY2VudGlsZXNfY29tcGFyaXNvbiksICJrZXkiLCAidHlwZSIsICJ2YWx1ZSIsICd2YWx1ZV9wZXJjJyksdHlwZT09IjUwJSIpLCBzZWxlY3QoYWN0dWFsX21peF8yMDE4X3JlZHVjZWQsIC1YMSwgLXllYXIpKSAlPiUgZ2dwbG90KGFlcyh4PWtleSwgeT12YWx1ZV9wZXJjLCBmaWxsPXR5cGUpKSArIHN0YXRfc3VtbWFyeShnZW9tID0gImJhciIsIGZ1bi55ID0gbWVhbiwgcG9zaXRpb24gPSAiZG9kZ2UyIikrIHN0YXRfc3VtbWFyeShnZW9tID0gImVycm9yYmFyIiwgZnVuLmRhdGEgPSBtZWFuX3NlLCBwb3NpdGlvbiA9ICJkb2RnZTIiKSArIGdndGl0bGUoIkJlc3QgcGFyYW1ldGVyIGNvbWJpbmF0aW9ucyBmcm9tIGZpbmFsIGdlbmV0aWMgYWxnb3JpdGhtIHJ1biIpCgpgYGAKCgojIyBCRVNUIFBBUkFNRVRFUiBDT01CSU5BVElPTiBGUk9NIEFMTCBSVU5TCgpgYGB7cn0KYmVzdF83NV9wZXJjZW50aWxlX2FsbF9ydW5zID0gcnVuXzEgJT4lIGdyb3VwX2J5KGluZGl2aWR1YWxfYywgaW5kaXZpZHVhbF9tKSAlPiUgc3VtbWFyaXNlKG51bWJlcj1uKCksIHF1YW50aWxlcyA9IGxpc3QoZW5mcmFtZShxdWFudGlsZShyZXdhcmQsIHByb2JzPWMoMC4yNSwwLjUsMC43NSwgMC45LCAxLjApKSkpKSAlPiUgdW5uZXN0ICU+JSB1bmdyb3VwKCkgJT4lIGZpbHRlcihudW1iZXI+MTApICU+JSBncm91cF9ieShuYW1lKSAlPiVmaWx0ZXIocmFuayh2YWx1ZSwgdGllcy5tZXRob2Q9ImZpcnN0Iik9PTEpCgpwID0gZGRwbHkoYmVzdF83NV9wZXJjZW50aWxlX2FsbF9ydW5zLCAuKG5hbWUpLCB0cmFuc2Zvcm0sIHk9Z2V0X2xpbmUoaW5kaXZpZHVhbF9jLCBpbmRpdmlkdWFsX20pLCB4PWdldF94KCkpICAlPiUgZ2dwbG90KCkgKyBnZW9tX2xpbmUoYWVzKHg9eC4xLCB5PXgsIGNvbG9yPW5hbWUpKSsgc3RhdF9zbW9vdGgoZGF0YT1wZGNfZXhhbXBsZSwgYWVzKHg9c2VnbWVudF9kZW1hbmQsIHk9YWNjZXB0ZWRfcHJpY2UpLCBtZXRob2Q9ImxtIiwgY29sPSJ5ZWxsb3ciKSArIGdlb21fcG9pbnQoZGF0YT1wZGNfZXhhbXBsZSwgYWVzKHg9c2VnbWVudF9kZW1hbmQsIHk9YWNjZXB0ZWRfcHJpY2UpKSArIHhsYWIoIkRlbWFuZCAoTVcpIikgKyB5bGFiKCJBY2NlcHRlZCBQcmljZSIpICsgZ2d0aXRsZSgiUGVyY2VudGlsZXMgZnJvbSBhbGwgcnVucyIpCnAkbGFiZWxzJGZpbGwgPC0gIlBlcmNlbnRpbGVzIgpwcmludChwKQpgYGAKYGBge3J9CmJlc3RfcGVyY2VudGlsZXNfY29tcGFyaXNvbiA9IGlubmVyX2pvaW4ocnVuXzEsIGJlc3RfNzVfcGVyY2VudGlsZV9hbGxfcnVucywgYnk9YygnaW5kaXZpZHVhbF9jJywgJ2luZGl2aWR1YWxfbScpKSAlPiUgZ2F0aGVyKCJrZXkiLCAidmFsdWUiLCAiY29hbCIsICJjY2d0IiwgIndpbmQiLCAibnVjbGVhciIsICJzb2xhciIpICU+JSBncm91cF9ieShpZCkgJT4lIG11dGF0ZSh2YWx1ZV9wZXJjID0gdmFsdWUvc3VtKHZhbHVlKSkgJT4lIG11dGF0ZSh0eXBlPW5hbWUpCgpjb21wYXJpc29uX3BlcmNlbnRpbGVzID0gcmJpbmQoc2VsZWN0KHVuZ3JvdXAoYmVzdF9wZXJjZW50aWxlc19jb21wYXJpc29uKSwgImtleSIsICJ0eXBlIiwgInZhbHVlIiwgJ3ZhbHVlX3BlcmMnKSwgc2VsZWN0KGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCAtWDEsIC15ZWFyKSkKCmNvbXBhcmlzb25fcGVyY2VudGlsZXMgJT4lIGdncGxvdChhZXMoeD1rZXksIHk9dmFsdWVfcGVyYywgZmlsbD10eXBlKSkgKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJiYXIiLCBmdW4ueSA9IG1lYW4sIHBvc2l0aW9uID0gImRvZGdlMiIpKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJlcnJvcmJhciIsIGZ1bi5kYXRhID0gbWVhbl9zZSwgcG9zaXRpb24gPSAiZG9kZ2UyIikKYGBgCmBgYHtyfQpyYmluZChmaWx0ZXIoc2VsZWN0KHVuZ3JvdXAoYmVzdF9wZXJjZW50aWxlc19jb21wYXJpc29uKSwgImtleSIsICJ0eXBlIiwgInZhbHVlIiwgJ3ZhbHVlX3BlcmMnKSx0eXBlPT0iNzUlIiB8IHR5cGU9PSI5MCUiKSwgc2VsZWN0KGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCAtWDEsIC15ZWFyKSkgJT4lIGdncGxvdChhZXMoeD1rZXksIHk9dmFsdWVfcGVyYywgZmlsbD10eXBlKSkgKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJiYXIiLCBmdW4ueSA9IG1lYW4sIHBvc2l0aW9uID0gImRvZGdlMiIpKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJlcnJvcmJhciIsIGZ1bi5kYXRhID0gbWVhbl9zZSwgcG9zaXRpb24gPSAiZG9kZ2UyIikgKyBnZ3RpdGxlKCJCZXN0IHBhcmFtZXRlciBjb21iaW5hdGlvbnMgZnJvbSBhbGwgZ2VuZXRpYyBhbGdvcml0aG0gcnVucyIpCgpgYGAKCiMjIENhbGN1bGF0aW9uIG9mIGJlc3QgcXVhbnRpbGUgdG8gc2VsZWN0IGNvbWJpbmF0aW9uIG9mIHBhcmFtZXRlcnMgZm9yIGxvd2VzdCByZXdhcmQKCmBgYHtyfQpiZXN0X3BhcmFtcyA9IHBhcmFtc19tb3JlX3RoYW5fMTAgJT4lIGdyb3VwX2J5KGluZGl2aWR1YWxfYywgaW5kaXZpZHVhbF9tKSAlPiUgZ2F0aGVyKCJrZXkiLCAidmFsdWUiLCAiY29hbCIsICJjY2d0IiwgIndpbmQiLCAibnVjbGVhciIsICJzb2xhciIpICU+JSBncm91cF9ieShpZCkgJT4lIG11dGF0ZSh2YWx1ZV9wZXJjID0gdmFsdWUvc3VtKHZhbHVlKSkgJT4lIGdyb3VwX2J5KGlkLCBrZXkpICU+JSBpbm5lcl9qb2luKGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCBieT0ia2V5IikgJT4lIG11dGF0ZShkaWZmX3BlcmMgPSBhYnModmFsdWVfcGVyYy54LXZhbHVlX3BlcmMueSkpICU+JSBncm91cF9ieShpZCkgJT4lIHN1bW1hcmlzZShtZWFuX2RpZmZfcGVyYyA9IG1lYW4oZGlmZl9wZXJjKSwgaW5kaXZpZHVhbF9jID0gbWVhbihpbmRpdmlkdWFsX2MpLCBpbmRpdmlkdWFsX20gPSBtZWFuKGluZGl2aWR1YWxfbSkpICU+JSBmaWx0ZXIocmFuayhtZWFuX2RpZmZfcGVyYywgdGllcy5tZXRob2Q9ImZpcnN0Iik9PTEpCgpiZXN0X3BhcmFtc19jb21wYXJpc29uID0gZmlsdGVyKHJ1bl8xLG5lYXIoeD1pbmRpdmlkdWFsX2MsIHk9YmVzdF9wYXJhbXMkaW5kaXZpZHVhbF9jWzFdLCB0b2w9MC4wMDAwMSksbmVhcih4PWluZGl2aWR1YWxfbSwgeT1iZXN0X3BhcmFtcyRpbmRpdmlkdWFsX21bMV0sIHRvbD0wLjAwMDAxKSkgJT4lIGdhdGhlcigia2V5IiwgInZhbHVlIiwgImNvYWwiLCAiY2NndCIsICJ3aW5kIiwgIm51Y2xlYXIiLCAic29sYXIiKSAlPiUgZ3JvdXBfYnkoaWQpICU+JSBtdXRhdGUodmFsdWVfcGVyYyA9IHZhbHVlL3N1bSh2YWx1ZSkpICU+JSBtdXRhdGUodHlwZT0icHJlZGljdGVkIikKCmNvbXBhcmlzb25fYmVzdF9wYXJhbXMgPSByYmluZChzZWxlY3QodW5ncm91cChiZXN0X3BhcmFtc19jb21wYXJpc29uKSwgImtleSIsICJ0eXBlIiwgInZhbHVlIiwgJ3ZhbHVlX3BlcmMnKSwgc2VsZWN0KGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCAtWDEsIC15ZWFyKSkKCmNvbXBhcmlzb25fYmVzdF9wYXJhbXMgJT4lIGdncGxvdChhZXMoeD1rZXksIHk9dmFsdWVfcGVyYywgZmlsbD10eXBlKSkgKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJiYXIiLCBmdW4ueSA9IG1lYW4sIHBvc2l0aW9uID0gImRvZGdlMiIpKyBzdGF0X3N1bW1hcnkoZ2VvbSA9ICJlcnJvcmJhciIsIGZ1bi5kYXRhID0gbWVhbl9zZSwgcG9zaXRpb24gPSAiZG9kZ2UyIikKCgoKIyMgR2V0dGluZyBNQVBFIGZvciBiZXN0IHBhcmFtCgpwcmVkaWN0ZWRfbWVhbnNfYmVzdF9wYXJhbSA9IGZpbHRlcihydW5fMSxuZWFyKHg9aW5kaXZpZHVhbF9jLCB5PWJlc3RfcGFyYW1zJGluZGl2aWR1YWxfY1sxXSwgdG9sPTAuMDAwMDEpLG5lYXIoeD1pbmRpdmlkdWFsX20sIHk9YmVzdF9wYXJhbXMkaW5kaXZpZHVhbF9tWzFdLCB0b2w9MC4wMDAwMSkpICU+JSBnYXRoZXIoImtleSIsICJ2YWx1ZSIsICJjb2FsIiwgImNjZ3QiLCAid2luZCIsICJudWNsZWFyIiwgInNvbGFyIikgJT4lIGdyb3VwX2J5KGlkKSAlPiUgbXV0YXRlKHZhbHVlX3BlcmMgPSB2YWx1ZS9zdW0odmFsdWUpKSAlPiUgbXV0YXRlKHR5cGU9InByZWRpY3RlZCIpICU+JSBncm91cF9ieShpbmRpdmlkdWFsX2MsIGluZGl2aWR1YWxfbSwga2V5KSAlPiUgc3VtbWFyaXNlKHByZWRpY3RlZF9wZXJjID0gbWVhbih2YWx1ZV9wZXJjKSkKCnByaW50KHBhc3RlKCJNQVBFID0gIiwgTUFQRShwcmVkaWN0ZWRfbWVhbnNfYmVzdF9wYXJhbSRwcmVkaWN0ZWRfcGVyYywgYWN0dWFsX21peF8yMDE4X3JlZHVjZWQkdmFsdWVfcGVyYykpKQoKYGBgCgpgYGB7cn0KcCA9IGJlc3RfcGFyYW1zICU+JSB0cmFuc2Zvcm0oeT1nZXRfbGluZShpbmRpdmlkdWFsX2MsIGluZGl2aWR1YWxfbSksIHg9Z2V0X3goKSkgICU+JSBnZ3Bsb3QoKSArIGdlb21fbGluZShhZXMoeD14LjEsIHk9eCkpKyBzdGF0X3Ntb290aChkYXRhPXBkY19leGFtcGxlLCBhZXMoeD1zZWdtZW50X2RlbWFuZCwgeT1hY2NlcHRlZF9wcmljZSksIG1ldGhvZD0ibG0iLCBjb2w9InllbGxvdyIpICsgZ2VvbV9wb2ludChkYXRhPXBkY19leGFtcGxlLCBhZXMoeD1zZWdtZW50X2RlbWFuZCwgeT1hY2NlcHRlZF9wcmljZSkpICsgeGxhYigiRGVtYW5kIChNVykiKSArIHlsYWIoIkFjY2VwdGVkIFByaWNlIikgKyBnZ3RpdGxlKCJCZXN0IHBhcmFtYXRlciBjb21iaW5hdGlvbiBwcmljZSBjdXJ2ZSIpCnAkbGFiZWxzJGZpbGwgPC0gIlBlcmNlbnRpbGVzIgpwcmludChwKQoKYGBgCgoKCmBgYHtyfQphY2N1cmF0ZV9hcmVhICU+JSBnZ3Bsb3QoKSArIGdlb21faGlzdG9ncmFtKGFlcyhpbmRpdmlkdWFsX20pKSArIHhsaW0oMC4wMDE1LCAwLjAwMjUpCgpgYGAKYGBge3J9CmFjY3VyYXRlX2FyZWEgJT4lIGdncGxvdChhZXMoaW5kaXZpZHVhbF9jKSkgKyBnZW9tX2hpc3RvZ3JhbSgpIAoKYGBgCgpgYGB7cn0KcD1nZ3Bsb3QocnVuXzEsIGFlcyh5PWluZGl2aWR1YWxfbSwgeD1pbmRpdmlkdWFsX2MpKSArIHN0YXRfc3VtbWFyeV9oZXgoYWVzKHogPSB0aW1lX3Rha2VuKSwgYmlucz0xMCkgKyBmYWNldF93cmFwKH4gcnVuX251bWJlcikgKyBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gcm91bmQoc2VxKG1pbihydW5fMSRpbmRpdmlkdWFsX2MpLCBtYXgocnVuXzEkaW5kaXZpZHVhbF9jKSwgYnkgPSAyNSksMSkpCnAkbGFiZWxzJGZpbGwgPC0gIlRpbWUgXG4gVGFrZW4iCnByaW50KHApCgpnZ3NhdmUoJ34vRGVza3RvcC90aW1lLXRha2VuLXBhcmFtZXRlcnMucG5nJykKYGBgCgpgYGB7cn0KcnVuXzEgJT4lIGFycmFuZ2UoZGVzYyhydW5fbnVtYmVyKSkgJT4lIGdncGxvdChhbHBoYT0wLjEsIGFlcyh5PWluZGl2aWR1YWxfbSwgeD1pbmRpdmlkdWFsX2MpKSArIGdlb21fcG9pbnQoYWVzKGNvbG9yPXJ1bl9udW1iZXIsIHNpemU9dGltZV90YWtlbikpCmBgYAoKYGBge3J9CnJ1bl8xICU+JSBmaWx0ZXIoaW5kaXZpZHVhbF9jPC0zLCBpbmRpdmlkdWFsX20gPDAuMDAyNSwgaW5kaXZpZHVhbF9tID4gMC4wMDE1KSAlPiUgZ2dwbG90KGFlcyh4PWFzLmZhY3RvcihydW5fbnVtYmVyKSwgeT10aW1lX3Rha2VuKSkgKyBnZW9tX3Zpb2xpbigpCmBgYApgYGB7cn0KZ2dwbG90KGRhdGE9cnVuXzEsIGFlcyh4PWFzLmZhY3RvcihydW5fbnVtYmVyKSwgeT1yZXdhcmQpKStnZW9tX2JveHBsb3QoKStnZW9tX2ppdHRlcihwb3NpdGlvbj1wb3NpdGlvbl9qaXR0ZXIoMC4yKSwgYWxwaGE9MC41LCBjb2xvcj0nYmx1ZScpCmBgYAoKCmBgYHtyfQpydW5fMSAlPiUgZmlsdGVyKGluZGl2aWR1YWxfYzwtMywgaW5kaXZpZHVhbF9tIDwwLjAwMjUsIGluZGl2aWR1YWxfbSA+IDAuMDAxNSkgJT4lIGdncGxvdChhZXMoeD1hcy5mYWN0b3IocnVuX251bWJlciksIHk9cmV3YXJkKSkgKyBnZW9tX3Zpb2xpbigpCgpgYGAKCmBgYHtyfQpnZ3Bsb3QocnVuXzEsIGFlcyh4PXJld2FyZCwgeT10aW1lX3Rha2VuKSkrc3RhdF9zbW9vdGgobWV0aG9kID0gImxtIikrZ2VvbV9wb2ludCgpK3hsYWIoIkFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IiKQoKYGBgCgpgYGB7cn0KZGlmX3N1bSA9IHJ1bl8xX2xvbmcgJT4lIGlubmVyX2pvaW4oYWN0dWFsX21peF8yMDE4X3JlZHVjZWQsIGJ5PSdrZXknKSAlPiUgZ3JvdXBfYnkoaWQsa2V5KSAlPiUgbXV0YXRlKHRvdGFsX2RpZmZlcmVuY2UgPSB2YWx1ZS54LXZhbHVlLnkpICU+JSBkZHBseSguKGlkLCBrZXkpLCBzdW1tYXJpc2UsIGRpZmZlcmVuY2Vfc3VtID0gc3VtKHRvdGFsX2RpZmZlcmVuY2UpLCB0aW1lX3Rha2VuPXRpbWVfdGFrZW4sIHJld2FyZD1yZXdhcmQsIHJ1bl9udW1iZXI9cnVuX251bWJlcikgJT4lIGdyb3VwX2J5KGlkKSAlPiUgc3VtbWFyaXNlKHRvdF9kaWZmID0gc3VtKGRpZmZlcmVuY2Vfc3VtKSwgdGltZV90YWtlbj1tZWFuKHRpbWVfdGFrZW4pLCByZXdhcmQ9bWVhbihyZXdhcmQpLCBydW5fbnVtYmVyPW1lYW4ocnVuX251bWJlcikpCgpwbG90X2x5KGRhdGE9ZGlmX3N1bSwgeD1+dG90X2RpZmYsIHk9fnRpbWVfdGFrZW4sIHo9fnJld2FyZCwgdHlwZT0ic2NhdHRlcjNkIiwgbW9kZT0ibWFya2VycyIsIGNvbG9yPX5ydW5fbnVtYmVyKSAKYGBgCgpgYGB7cn0KZ2dwbG90KCkrZ2VvbV9wb2ludChkYXRhPXJ1bl8xLCBhZXMoeD10aW1lX3Rha2VuLCB5PXJld2FyZCwgY29sb3I9cnVuX251bWJlcikpCmBgYAoKYGBge3J9CnJ1bl8xCgpydW5fMV9sb25nID0gZ2F0aGVyKHJ1bl8xLCAia2V5IiwgInZhbHVlIiwgImNvYWwiLCAiY2NndCIsICJ3aW5kIiwgIm51Y2xlYXIiLCAic29sYXIiKQoKIyBydW5fMV9sb25nICU+JSBpbm5lcl9qb2luKGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkLCBieT0na2V5JykgJT4lIGdyb3VwX2J5KGlkLGtleSkgJT4lIG11dGF0ZSh0b3RhbF9kaWZmZXJlbmNlID0gdmFsdWUueC12YWx1ZS55KSAlPiUgZ3JvdXBfYnkoaWQsIGtleSkgJT4lIHN1bW1hcmlzZShkaWZmZXJlbmNlX3N1bSA9IHN1bSh0b3RhbF9kaWZmZXJlbmNlKSkKCmdncGxvdChkYXRhPWRpZl9zdW0sIGFlcyh4PXRvdF9kaWZmLCB5PXRpbWVfdGFrZW4sIGNvbG9yPXJld2FyZCkpICsgZ2VvbV9wb2ludCgpK2dlb21fc21vb3RoKCkreGxpbSgtMTEwMDAsMTAwMDApCgojIGFjdHVhbF9taXhfMjAxOF9yZWR1Y2VkCmBgYApgYGB7cn0KZ2dwbG90KGRhdGE9ZGlmX3N1bSwgYWVzKHg9dG90X2RpZmYsIGNvbG9yPXRpbWVfdGFrZW4sIHk9cmV3YXJkKSkgKyBnZW9tX3BvaW50KCkrZ2VvbV9zbW9vdGgoKSt4bGltKC0xMTAwMCwxMDAwMCkKYGBgCgpgYGB7cn0KZ2dwbG90KHJ1bl8xLCBhZXMoeD1ydW5fbnVtYmVyLCB5PXRpbWVfdGFrZW4sIGNvbG9yPXJld2FyZCkpICsgZ2VvbV9wb2ludCgpICsKICAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScpCgpgYGA=